package nl.weeaboo.sh.objects;

import static nl.weeaboo.sh.level.Level.fromTileCoord;
import static nl.weeaboo.sh.level.Level.toTileCoord;

import java.awt.Point;
import java.awt.Rectangle;

import nl.weeaboo.game.gl.GLImage;
import nl.weeaboo.game.gl.GLManager;
import nl.weeaboo.game.input.UserInput;
import nl.weeaboo.sh.Game;
import nl.weeaboo.sh.danmaku.SpellUtil;
import nl.weeaboo.sh.level.ActiveTiles;
import nl.weeaboo.sh.level.Level;
import nl.weeaboo.sh.level.Room;
import nl.weeaboo.sh.level.Tile;
import nl.weeaboo.sh.model.DamageRect;
import nl.weeaboo.sh.view.CharaImage;

public class Monster extends Sprite {
	
	protected static final float GRAVITY_ACCEL        = 1.00f;
	protected static final float FLY_GRAVITY_ACCEL    = 0.00f; //0.50f;
	protected static final float groundFriction       = 0.25f;
	protected static final int   maxStunFlash         = 20;
	public    static final int   hitInvincibilityTime = 15;
	
	protected CharaImage image;
	protected Platform platform;
	protected float speedX, speedY;
	protected boolean grounded;
	protected boolean floating;
	protected int stunFlash;
	protected int dir;
	
	protected GLImage healthBarFullI;
	protected GLImage healthBarEmptyI;
	
	public Monster(Game game, Level level, CharaImage image) {
		super(game, level);
		
		setImage(image);
		
		dir = 1;
		
		healthBarFullI = game.getImage("object/objectFx.png#healthBarFull", false);
		healthBarEmptyI = game.getImage("object/objectFx.png#healthBarEmpty", false);
	}

	//Functions		
	public boolean damage(int d) {		
		if (!isInvincible() && d > 0) {
			if (super.damage(d)) {
				//invincibilityTime may have changed as a result of super.damage(..)
				invincibilityTime = Math.max(invincibilityTime, hitInvincibilityTime);
				stunFlash = maxStunFlash;
				return true;
			}
		}
		return false;
	}
	
	public void update(UserInput input, float dt) {
		super.update(input, dt);
		
		int ts = Level.tileSize;

		if (invincibilityTime > 0) invincibilityTime--;
		if (stunFlash > 0) stunFlash--;
		
		//Gravity
		if (!floating) {
			speedY += GRAVITY_ACCEL;
		}

		//Terminal velocity (<= tilesize)
		speedX = Math.max(Math.min(speedX, ts), -ts);
		speedY = Math.max(Math.min(speedY, ts), -ts);

		collide(Math.round(speedX), Math.round(speedY));		
	}	

	protected void limitMovementRoom(Room room) {
		if (room != null) {
			Rectangle lhb = getLevelHitBox();
			
			if (room.isLeftWall()) x = Math.max(Level.fromTileCoord(room.getX()) - lhb.x, x);
			if (room.isRightWall()) x = Math.min(Level.fromTileCoord(room.getX()+room.getWidth()) - 1 - lhb.width - lhb.x, x);
			if (room.isTopWall()) y = Math.max(Level.fromTileCoord(room.getY()) - lhb.y, y);
			if (room.isBottomWall()) y = Math.min(Level.fromTileCoord(room.getY()+room.getHeight()) - 1 - lhb.height - lhb.y, y);
		}		
	}
	
	protected void drawImage(GLManager glm) {
		if (stunFlash > 0 && stunFlash >= maxStunFlash/2) {
			glm.pushColor();
			float s = 1.0f - SpellUtil.fastSin(128 * stunFlash / maxStunFlash);
			glm.mixColor(1, s, s, 1);
		}
		
		image.draw(glm, dir==-1);

		if (stunFlash > 0 && stunFlash >= maxStunFlash/2) {
			glm.popColor();
		}		
	}
	
	public void drawForeground(GLManager glm) {
		if (stunFlash > 0) {
			GLImage bg = healthBarEmptyI;
			GLImage fg = healthBarFullI;
			
			int tx = x - 24;
			int ty = y + getLevelHitBox().y - 24;
			
			bg.draw(glm, tx, ty);
			fg.draw(glm, tx, ty, fg.getWidth() * getHp() / getMaxHp(), fg.getHeight());
			fg.setTilingU(0, 1);			
		}		
		
		/*//Draw Damage Rect
		glm.setTexture(null);
		glm.setColor(Color.RED);		
		for (Rectangle r : getDamageRects()) {
			GLDraw.fillRect(glm.getGL(), x+r.x, y+r.y, r.width, r.height);
		}*/
	}
	
	protected boolean collideH(int dx, int dy) {
		int ts = Level.tileSize;
		
		Rectangle levelHitBox = getLevelHitBox();
		int a = levelHitBox.x;
		int b = a + levelHitBox.width;
		int c = levelHitBox.y;
		int d = c + levelHitBox.height;
		
		ActiveTiles at = level.getActiveTiles();
		int atx = at.getX();
		int aty = at.getY();
		Tile tile;
		
		int xinc = Math.min(ts, Math.max(-ts, dx));
		int newX = x + xinc;
		int newY = y;

		int slopedTy = Integer.MAX_VALUE;
		
		if (dy >= 0) {
			int z = Integer.MAX_VALUE;			

			int oldCx = toTileCoord(x);
			int newCx = toTileCoord(newX);
			if (oldCx != newCx) {
				int ty = toTileCoord(y + d - 1);
				tile = at.getTile(oldCx-atx, ty-aty);
				if (tile != null && tile.getModel().isSloped()) {	
					int tx = (newCx > oldCx ? Level.tileSize-1 : 0);
					z = Math.min(z, fromTileCoord(ty+1) - tile.getHeight(tx) - d - 1);
					
					slopedTy = Math.min(slopedTy, ty);
				}
			}
			
			int cx  = toTileCoord(newX);
			int ty1 = toTileCoord(newY + c);
			int ty2 = toTileCoord(newY + d - 1);
			for (int ty = ty1; ty <= ty2+1; ty++) {
				tile = at.getTile(cx-atx, ty-aty);
				if (tile != null && tile.getModel().isSloped()) {					
					int thx = newX - fromTileCoord(cx);
					z = Math.min(z, fromTileCoord(ty+1) - tile.getHeight(thx) - d - 1);
					
					slopedTy = Math.min(slopedTy, ty);
				}
			}
			
			if (z != Integer.MAX_VALUE && z <= y+4+Math.abs(dx)) {
				newY = z;
				grounded = true;
			}
		}
		
		int tx1 = toTileCoord(newX + a);
		int tx2 = toTileCoord(newX + b);
		int ty1 = toTileCoord(newY + c);
		int ty2 = toTileCoord(newY + d - 1);
		int sideX = (dx >= 0 ? tx2 : tx1);
				
		for (int n = ty1; n <= ty2; n++) {
			if (n >= slopedTy) continue;
			
			tile = at.getTile(sideX-atx, n-aty);

			if (tile != null && tile.isSolidH(dx) && !tile.getModel().isSloped()) {
				if (dx >= 0) {
					int z = fromTileCoord(tx2) - b - 1;						
					if (z < newX) {
						newX = z;
						onHitWall(1, tile.isWallJumpable());
					}
				} else {
					int z = fromTileCoord(tx1+1) - a;						
					if (z > newX) {
						newX = z;
						onHitWall(-1, tile.isWallJumpable());
					}
				}		
			}				
		}
		
		grounded = grounded || newY != y;
		if (grounded) {
			speedY = 0;
		}
		y = newY;

		boolean collided = x != newX;
		if (collided) {
			x = newX;
			return true;
		}

		return false;
	}

	protected boolean collideV(int dy, boolean ignorePlatforms) {
		int ts = Level.tileSize;
		
		Rectangle levelHitBox = getLevelHitBox();
		int a = levelHitBox.x;
		int b = a + levelHitBox.width;
		int c = levelHitBox.y;
		int d = c + levelHitBox.height;
				
		ActiveTiles at = level.getActiveTiles();
		int atx = at.getX();
		int aty = at.getY();
		Tile tile;
		
		int yinc = Math.min(ts, Math.max(-ts, dy));
		int newY = y + yinc;

		int tx1 = toTileCoord(x + a);
		int tx2 = toTileCoord(x + b);
		int ty1 = toTileCoord(newY + c);
		int ty2 = toTileCoord(newY + d);
		int sideY = (dy >= 0 ? ty2 : ty1);
		
		Tile tiles[] = new Tile[tx2-tx1+1];
		int z[] = new int[tiles.length];
		boolean slopedCol = false;
		
		for (int n = tx1; n <= tx2; n++) {
			tile = at.getTile(n-atx, sideY-aty);			
			if (tile == null || !tile.isSolidV(dy) || (ignorePlatforms && !tile.isSolidV(-1))) {
				continue;
			}

			if (tile.getModel().isSloped()) slopedCol = true;
			tiles[n-tx1] = tile;			
			
			if (dy >= 0) {				
				int thx = x - fromTileCoord(n);
				z[n-tx1] = fromTileCoord(ty2+1) - tile.getHeight(thx) - d;
			} else {
				z[n-tx1] = fromTileCoord(ty1+1) - c;
			}
		}
		
		if (slopedCol) {
			int n = Math.max(0, Math.min(tiles.length-1, toTileCoord(x)-tx1));
			if (tiles[n] != null && z[n] <= newY
					&& (tiles[n].isSolidV(-1) || ty2 != toTileCoord(y+d-1)))
			{
				newY = z[n];
				onLand();
			}
		} else {
			for (int n = 0; n < tiles.length; n++) {
				if (tiles[n] == null) continue;
				
				if (dy >= 0) {
					if (z[n] <= newY && (tiles[n].isSolidV(-1) || ty2 != toTileCoord(y+d-1))) {
						newY = z[n];
						onLand();
					}
				} else {
					if (z[n] >= newY) {
						newY = z[n];
						onHitCeiling();
					}				
				}			
			}
		}

		boolean collided = y != newY;
		if (collided) {
			y = newY;
			return true;
		}
		return false;
	}
	
	protected void collide(int dx, int dy) {
		collide(dx, dy, false);
	}
	protected void collide(int dx, int dy, boolean ignorePlatforms) {	
		Level level = (Level)game.getLevel();
		Room oldRoom = level.getRoom(Level.toTileCoord(x), Level.toTileCoord(y));

		boolean wasGrounded = grounded;
		if (dy != 0) {
			grounded = false;
		}
		
		collideH(dx, dy);
		collideV(dy, ignorePlatforms);
		if (grounded) {			
			if (!wasGrounded) onLand();
		}

		limitMovementRoom(oldRoom);
	}

	protected void onHitWall(int dir, boolean wallJumpable) {
		speedX = 0;
	}
	protected void onHitCeiling() {
		speedY = 0;
	}
	protected void onLand() {
		speedY = 0;
		grounded = true;
	}
	
	public boolean onHit(Sprite hitter, int damage, Rectangle hitArea, DamageRect dr) {
		if (super.onHit(hitter, damage, hitArea, dr)) {
			return true;
		}
		return false;
	}
	
	//Getters
	public Point getImageOffset() {
		return image.getImageOffset(dir < 0);
	}
	public Rectangle getLevelHitBox() {
		return image.getLevelHitBox(dir < 0);
	}
	public Rectangle[] getHitRects() { return image.getHitRects(dir < 0); }
	public DamageRect[] getDamageRects() {
		if (stunFlash > 0) return Sprite.noDamageRects;
		return image.getDamageRects(dir < 0);
	}
	public DamageRect[] getSplashRects() {
		if (stunFlash > 0) return Sprite.noDamageRects;
		return image.getSplashRects(dir < 0);
	}
	public float getSpeedX() { return speedX; }
	public float getSpeedY() { return speedY; }
	public Rectangle getImageBounds(boolean mirror) {
		Rectangle ib = image.getImageBounds(mirror);
		return new Rectangle(x+ib.x, y+ib.y, ib.width, ib.height);
	}

	//Setters
	public void setSpeed(float sx, float sy) {
		speedX = sx;
		speedY = sy;
	}
	public void setImage(CharaImage image) {
		this.image = image;
	}

}
